package fag.core.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import fag.core.collision.Hitbox;
import fag.core.geometry.Rectangle2D;

public class Animation implements Cloneable, Serializable {

	private static final long serialVersionUID = -1792859844592715379L;
	
	private String name = "Untitled";
	private List<Frame> frames = new ArrayList<Frame>();

	private boolean isLooping = true;
	private boolean hasPlayed = false;
	
	private double duration = 0;
	private double speed = 1.0;
	
	private double timestamp = 0;

	private int currentFrameIndex = 0;
	private int lastFrameIndex = -1;
	
	/**
	 * Constructeur par défaut.
	 */
	public Animation() {
	}
	
	/**
	 * Constructeur avec parametré.
	 * @param name Le nom de l'animation.
	 */
	public Animation(String name) {
		this.name = name;
	}
	
	/**
	 * Ajouter une frame à cette animation.
	 * @param frame La frame à ajouter.
	 * @return Vrai si l'ajout réussi, sinon faux.
	 */
	public boolean addFrame(Frame frame) {
		duration += frame.getDuration();
		return frames.add(frame);
	}
	
	/**
	 * Ajouter une frame à cette animation.
	 * @param index L'index où la frame doit être insérée.
	 * @param frame La frame à ajouter.
	 */
	public void addFrame(int index, Frame frame) {
		duration += frame.getDuration();
		frames.add(index, frame);
	}
	
	@Override
	public Animation clone() {

		Animation animation = null;
		
		try {
			animation = (Animation) super.clone();	
		}
		catch (CloneNotSupportedException cloneNotSupportedException) {
			cloneNotSupportedException.printStackTrace();
		}
		
		return animation;
	}
	
	/**
	 * Accesseur indiquant si la frame courante a changé.
	 * @return Vrai si la frame a changé, sinon faux.
	 */
	public boolean frameChanged() {
		return lastFrameIndex != currentFrameIndex;
	}
	
	/**
	 * Accesseur de la boîte englobante de la frame actuelle.
	 * @return La boîte englobante de la frame actuelle.
	 */
	public Rectangle2D getCurrentBounds() {
		return frames.get(currentFrameIndex).getBounds();
	}
	
	/**
	 * Accesseur de la liste des boîtes de collisions courantes.
	 * @return La boîte de collisions pour la frame actuelle de l'animation.
	 */
	public Hitbox getCurrentHitbox() {
		return frames.get(currentFrameIndex).hitbox;
	}
	
	/**
	 * Accesseur du sprite de la frame actuelle.
	 * @return Le sprite de la frame actuelle.
	 */
	public Sprite getCurrentSprite() {
		return frames.get(currentFrameIndex).getSprite();
	}
	
	/**
	 * Accesseur de la durée d'une animation.
	 * @return La durée de l'animation.
	 */
	public double getDuration() {
		return duration;
	}
	
	/**
	 * Accesseur du nom de l'animation.
	 * @return Le nom de l'animation.
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Accesseur de la vitesse de l'animation.
	 * @return La vitesse de l'animation.
	 */
	public double getSpeed() {
		return speed;
	}
	
	/**
	 * Accesseur de la valeur de vérité de bouclage de l'animation.
	 * @return Vrai si l'animation boucle, sinon faux.
	 */
	public boolean isLooping() {
		return isLooping;
	}
	
	/**
	 * Retirer l'élément à l'index indiqué.
	 * @param index L'index de l'élément à retirer.
	 * @return La frame retirée.
	 */
	public Frame removeFrame(int index) {
		duration -= frames.get(index).getDuration();
		return frames.remove(index);
	}
	
	/**
	 * Retirer la frame de l'animation.
	 * @param frame La frame à retirer.
	 * @return Vrai si la frame est retirée, sinon faux.
	 */
	public boolean removeFrame(Frame frame) {
		duration -= frame.getDuration();
		return frames.remove(frame);
	}
	
	/**
	 * Redémarrer l'animation.
	 */
	public void reset() {
		hasPlayed = false;
		timestamp = 0;
		currentFrameIndex = 0;
	}
	
	public void updateDuration() {
		
		duration = 0;
		for(Frame f : frames) {
			
			duration+= f.getDuration();
		}
	}
	
	/**
	 * Fixer si l'animation doit boucler ou pas.
	 * @param isLooping La valeur de vérité du bouclage de l'animation.
	 */
	public void setLoop(boolean isLooping) {
		this.isLooping = isLooping;
	}
	
	/**
	 * Fixer le nom de l'animation.
	 * @param name Le nom de l'animation.
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * Fixer la vitesse de l'animation.
	 * @param speed La vitesse de l'animation.
	 */
	public void setSpeed(double speed) {
		this.speed = speed;
	}
	
	/**
	 * Avancer l'animation.
	 * @param dt L'intervalle de temps écoulé depuis la dernière mise à jour de l'horloge.
	 */
	public void forward(double dt) {

		lastFrameIndex = currentFrameIndex;
		
		if(duration == 0)
			currentFrameIndex = 0;
		
		timestamp += speed * dt;
		
		if(timestamp > duration) {
			
			timestamp -= duration;
			hasPlayed = true;
		}
		
		if((isLooping == false) && (hasPlayed == true)) {
			
			return;
		}
		
		int cpt = 0;
		
		for(int i = 0; i < frames.size(); ++i) {
			
			if(timestamp <= frames.get(i).getDuration() + cpt) {
				
				
				currentFrameIndex = i;
				return;
			}
			else {
				
				cpt += frames.get(i).getDuration();
			}
		}
	}
	
	public ArrayList<Frame> getListFrame() {
		
		return (ArrayList<Frame>) frames;
	}
}
